/*
 * Copyright 2011 Open Source Applications Foundation
 * 
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *     http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.osaf.caldav4j.methods;

import java.io.IOException;
import java.io.InputStream;

import net.fortuna.ical4j.data.CalendarBuilder;
import net.fortuna.ical4j.data.ParserException;
import net.fortuna.ical4j.model.Calendar;

import org.apache.commons.httpclient.Header;
import org.apache.commons.httpclient.HttpConnection;
import org.apache.commons.httpclient.HttpException;
import org.apache.commons.httpclient.HttpState;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.webdav.lib.methods.DepthSupport;
import org.apache.webdav.lib.methods.HttpRequestBodyMethodBase;
import org.apache.webdav.lib.util.XMLDebugOutputer;
import org.osaf.caldav4j.CalDAVConstants;
import org.osaf.caldav4j.exceptions.CalDAV4JException;
import org.osaf.caldav4j.exceptions.CalDAV4JProtocolException;
import org.osaf.caldav4j.exceptions.DOMValidationException;
import org.osaf.caldav4j.model.request.CalDAVReportRequest;
import org.osaf.caldav4j.util.UrlUtils;
import org.osaf.caldav4j.util.XMLUtils;
import org.w3c.dom.Document;

/**
 * This method implements the REPORT method described in 
 *  caldav RFC4791. As it's requestBody is an XML document
 *  it takes as a parameter a class describing the document, then
 *  generates the body before passing it to executeMethod.
 *  
 *  so:
 *  1- set this.reportRequest 
 *  2- the class creates the body from the XML
 * 
 * This method differs from {@code CalDAVReportMethod} in that its response is expected to be a calendar, rather than
 * XML.
 *  
 * @author robipolli@gmail.com
 * @author <a href="mailto:markhobson@gmail.com">Mark Hobson</a>
 * @see CalDAVReportMethod
 */
public class CalendarCalDAVReportMethod extends HttpRequestBodyMethodBase implements DepthSupport, CalDAVConstants {
	
	// TODO: rationalise with CalDAVReportMethod
	
    private static final Log log = LogFactory
        .getLog(CalendarCalDAVReportMethod.class);
    
    // debug level 
    private int debug = 0;

    /**
     * XML Debug Outputer
     */
    private XMLDebugOutputer xo = new XMLDebugOutputer();
    
    private CalendarBuilder calendarBuilder = null;

    public CalendarBuilder getCalendarBuilder() {
		return calendarBuilder;
	}

	public void setCalendarBuilder(CalendarBuilder calendarBuilder) {
		this.calendarBuilder = calendarBuilder;
	}

	/** this is the XML document that will be generated by @link{generateRequestBody()} */
    private CalDAVReportRequest reportRequest; 
    
    private int depth = DEPTH_1;
    
    protected CalendarCalDAVReportMethod() {

    }

    protected CalendarCalDAVReportMethod(String path, CalDAVReportRequest reportRequest) {
        this.reportRequest = reportRequest;
        setPath(path);
    }

    /**
     * Depth setter.
     *
     * @param depth New depth value
     */
    public void setDepth(int depth) {
        this.depth = depth;
    }

    /**
     * Depth getter.
     *
     * @return int depth value
     */
    public int getDepth() {
        return depth;
    }
    
    @Override
	public String getName() {
        return CalDAVConstants.METHOD_REPORT;
    }

    public CalDAVReportRequest getReportRequest() {
        return reportRequest;
    }

    public void setReportRequest(CalDAVReportRequest reportRequest) {
        this.reportRequest = reportRequest;
    }
    
    
    /**
     * Generate additional headers needed by the request.
     *
     * @param state State token
     * @param conn The connection being used to make the request.
     */
    @Override
	public void addRequestHeaders(HttpState state, HttpConnection conn)
    throws IOException, HttpException {

        super.addRequestHeaders(state, conn);

        switch (depth) {
        case DEPTH_0:
            super.setRequestHeader("Depth", "0");
            break;
        case DEPTH_1:
            super.setRequestHeader("Depth", "1");
            break;
        case DEPTH_INFINITY:
            super.setRequestHeader("Depth", CalDAVConstants.INFINITY_STRING);
            break;
        }

        if (getRequestHeader(HEADER_CONTENT_TYPE) == null) {
        	addRequestHeader(HEADER_CONTENT_TYPE,CONTENT_TYPE_TEXT_XML);
        }
    }

    // TODO: centralise; copied from XMLResponseMethodBase
    /**
     * Return the length (in bytes) of my request body, suitable for use in a
     * <tt>Content-Length</tt> header.
     *
     * <p>
     * Return <tt>-1</tt> when the content-length is unknown.
     * </p>
     *
     * <p>
     * This implementation returns <tt>0</tt>, indicating that the request has
     * no body.
     * </p>
     *
     * @return <tt>0</tt>, indicating that the request has no body.
     */
    @Override
	protected int getRequestContentLength() {
        if (!isRequestContentAlreadySet()) {
            String contents = generateRequestBody();
            // be nice - allow overriding functions to return null or empty
            // strings for no content.
            if (contents == null)
                contents = "";

            setRequestBody(contents);
            

            if (debug > 0) {
                System.out.println("\n>>>>>>>  to  server  ---------------------------------------------------");
				System.out.println(getName() + " " +
				   getPath() + (getQueryString() != null ? "?" + getQueryString() : "") + " " + "HTTP/1.1");
        
				   Header[] headers = getRequestHeaders();
				   for (int i = 0; i < headers.length; i++) {
					   Header header = headers[i];
					   System.out.print(header.toString());
				   }
				System.out.println("Content-Length: "+super.getRequestContentLength());
				   
				if (this instanceof DepthSupport) {
					System.out.println("Depth: "+((DepthSupport)this).getDepth());
				}

                System.out.println();
                xo.print(contents);
                System.out.println("------------------------------------------------------------------------");
            }

        }

        return super.getRequestContentLength();
    }

    /**
     * Generates a request body from the calendar query.
     */
    protected String generateRequestBody() {
        Document doc = null;
        try {
            doc = reportRequest.createNewDocument(XMLUtils
                    .getDOMImplementation());
        } catch (DOMValidationException domve) {
            log.error("Error trying to create DOM from CalDAVReportRequest: ", domve);
            throw new RuntimeException(domve);
        }
        return XMLUtils.toPrettyXML(doc);
    }
    
    // TODO: centralise; copied from GetMethod
    public Calendar getResponseBodyAsCalendar()  throws
		    ParserException, CalDAV4JException {
		Calendar ret = null;
		InputStream stream = null;
		try {
		    Header header = getResponseHeader(CalDAVConstants.HEADER_CONTENT_TYPE);
		    String contentType = header.getValue();
		    if (contentType.startsWith(CalDAVConstants.CONTENT_TYPE_CALENDAR)) {
		        stream = getResponseBodyAsStream();
		        ret =  calendarBuilder.build(stream);
		        return ret;		        
		    }
		
		    log.error("Expected content-type text/calendar. Was: " + contentType);
		    throw new CalDAV4JProtocolException("Expected content-type text/calendar. Was: " + contentType );
		} catch (IOException e) {
			if (stream != null ) { //the server sends the response
				if (log.isWarnEnabled()) {
					log.warn("Server response is " + UrlUtils.parseISToString(stream));
				}
			}
			throw new CalDAV4JException("Error retrieving and parsing server response at " + getPath(), e);
		}	       
	}
    
    // remove double slashes
    @Override
	public void setPath(String path) {
    	super.setPath(UrlUtils.removeDoubleSlashes(path));
    }
}